// physics/physics_ammo_terrain.js
import {document,window,requestAnimationFrame,cancelAnimationFrame,Event,core} from 'dhtml-weixin';
import * as THREE from './three/Three.js';

import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from './three/addons/controls/OrbitControls.js';
var requestId
Page({
  onShareAppMessage(){
    return getApp().onShare()
  },
  onShareTimeline(){
     return {title:"ThreeX 2.0"}
  },
	onUnload() {
		cancelAnimationFrame(requestId, this.canvas)
this.worker && this.worker.terminate()
		setTimeout(() => {
			if (this.renderer instanceof THREE.WebGLRenderer) {
				this.renderer.dispose()
				this.renderer.forceContextLoss()
				this.renderer.context = null
				this.renderer.domElement = null
				this.renderer = null
			}
		}, 0)
	},
	    webgl_touch(e) {
        const web_e = (window.platform=="devtools"?Event:Event0).fix(e)
        //window.dispatchEvent(web_e)
        //document.dispatchEvent(web_e)
        this.canvas.dispatchEvent(web_e)
    },
    onLoad() {
      document.createElementAsync("canvas", "webgl2").then(canvas => {
        this.canvas = canvas
        this.body_load(canvas).then()
      })
    },
    async body_load(canvas3d) {

      var Ammo = require("./three/ammo/index.js")
      
			// Heightfield parameters
			const terrainWidthExtents = 100;
			const terrainDepthExtents = 100;
			const terrainWidth = 128;
			const terrainDepth = 128;
			const terrainHalfWidth = terrainWidth / 2;
			const terrainHalfDepth = terrainDepth / 2;
			const terrainMaxHeight = 8;
			const terrainMinHeight = - 2;

			// Graphics variables
			let container, stats;
			let camera, scene, renderer;
			let terrainMesh;
			const clock = new THREE.Clock();

			// Physics variables
			let collisionConfiguration;
			let dispatcher;
			let broadphase;
			let solver;
			let physicsWorld;
			const dynamicObjects = [];
			let transformAux1;

			let heightData = null;
			let ammoHeightData = null;

			let time = 0;
			const objectTimePeriod = 3;
			let timeNextSpawn = time + objectTimePeriod;
			const maxNumObjects = 30;

			Ammo().then( function ( AmmoLib ) {

				Ammo = AmmoLib;

				init();
				animate();

			} );

			function init() {

				heightData = generateHeight( terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight );

				initGraphics();

				initPhysics();

			}

			function initGraphics() {

				container = document.getElementById( 'container' );

				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.shadowMap.enabled = true;
				container.appendChild( renderer.domElement );

				stats = new Stats();
				stats.domElement.style.position = 'absolute';
				stats.domElement.style.top = '0px';
				container.appendChild( stats.domElement );

				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xbfd1e5 );

				camera.position.y = heightData[ terrainHalfWidth + terrainHalfDepth * terrainWidth ] * ( terrainMaxHeight - terrainMinHeight ) + 5;

				camera.position.z = terrainDepthExtents / 2;
				camera.lookAt( 0, 0, 0 );

				const controls = new (window.platform=="devtools"?OrbitControls:OrbitControls0)( camera, renderer.domElement );
				controls.enableZoom = false;

				const geometry = new THREE.PlaneGeometry( terrainWidthExtents, terrainDepthExtents, terrainWidth - 1, terrainDepth - 1 );
				geometry.rotateX( - Math.PI / 2 );

				const vertices = geometry.attributes.position.array;

				for ( let i = 0, j = 0, l = vertices.length; i < l; i ++, j += 3 ) {

					// j + 1 because it is the y component that we modify
					vertices[ j + 1 ] = heightData[ i ];

				}

				geometry.computeVertexNormals();

				const groundMaterial = new THREE.MeshPhongMaterial( { color: 0xC7C7C7 } );
				terrainMesh = new THREE.Mesh( geometry, groundMaterial );
				terrainMesh.receiveShadow = true;
				terrainMesh.castShadow = true;

				scene.add( terrainMesh );

				const textureLoader = new THREE.TextureLoader();
				textureLoader.load( 'textures/grid.png', function ( texture ) {

					texture.wrapS = THREE.RepeatWrapping;
					texture.wrapT = THREE.RepeatWrapping;
					texture.repeat.set( terrainWidth - 1, terrainDepth - 1 );
					groundMaterial.map = texture;
					groundMaterial.needsUpdate = true;

				} );

				const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
				scene.add( ambientLight );

				const light = new THREE.DirectionalLight( 0xffffff, 3 );
				light.position.set( 100, 100, 50 );
				light.castShadow = true;
				const dLight = 200;
				const sLight = dLight * 0.25;
				light.shadow.camera.left = - sLight;
				light.shadow.camera.right = sLight;
				light.shadow.camera.top = sLight;
				light.shadow.camera.bottom = - sLight;

				light.shadow.camera.near = dLight / 30;
				light.shadow.camera.far = dLight;

				light.shadow.mapSize.x = 1024 * 2;
				light.shadow.mapSize.y = 1024 * 2;

				scene.add( light );


				window.addEventListener( 'resize', onWindowResize );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function initPhysics() {

				// Physics configuration

				collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
				dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
				broadphase = new Ammo.btDbvtBroadphase();
				solver = new Ammo.btSequentialImpulseConstraintSolver();
				physicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
				physicsWorld.setGravity( new Ammo.btVector3( 0, - 6, 0 ) );

				// Create the terrain body

				const groundShape = createTerrainShape();
				const groundTransform = new Ammo.btTransform();
				groundTransform.setIdentity();
				// Shifts the terrain, since bullet re-centers it on its bounding box.
				groundTransform.setOrigin( new Ammo.btVector3( 0, ( terrainMaxHeight + terrainMinHeight ) / 2, 0 ) );
				const groundMass = 0;
				const groundLocalInertia = new Ammo.btVector3( 0, 0, 0 );
				const groundMotionState = new Ammo.btDefaultMotionState( groundTransform );
				const groundBody = new Ammo.btRigidBody( new Ammo.btRigidBodyConstructionInfo( groundMass, groundMotionState, groundShape, groundLocalInertia ) );
				physicsWorld.addRigidBody( groundBody );

				transformAux1 = new Ammo.btTransform();

			}

			function generateHeight( width, depth, minHeight, maxHeight ) {

				// Generates the height data (a sinus wave)

				const size = width * depth;
				const data = new Float32Array( size );

				const hRange = maxHeight - minHeight;
				const w2 = width / 2;
				const d2 = depth / 2;
				const phaseMult = 12;

				let p = 0;

				for ( let j = 0; j < depth; j ++ ) {

					for ( let i = 0; i < width; i ++ ) {

						const radius = Math.sqrt(
							Math.pow( ( i - w2 ) / w2, 2.0 ) +
								Math.pow( ( j - d2 ) / d2, 2.0 ) );

						const height = ( Math.sin( radius * phaseMult ) + 1 ) * 0.5 * hRange + minHeight;

						data[ p ] = height;

						p ++;

					}

				}

				return data;

			}

			function createTerrainShape() {

				// This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored
				const heightScale = 1;

				// Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used.
				const upAxis = 1;

				// hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT"
				const hdt = 'PHY_FLOAT';

				// Set this to your needs (inverts the triangles)
				const flipQuadEdges = false;

				// Creates height data buffer in Ammo heap
				ammoHeightData = Ammo._malloc( 4 * terrainWidth * terrainDepth );

				// Copy the javascript height data array to the Ammo one.
				let p = 0;
				let p2 = 0;

				for ( let j = 0; j < terrainDepth; j ++ ) {

					for ( let i = 0; i < terrainWidth; i ++ ) {

						// write 32-bit float data to memory
						Ammo.HEAPF32[ ammoHeightData + p2 >> 2 ] = heightData[ p ];

						p ++;

						// 4 bytes/float
						p2 += 4;

					}

				}

				// Creates the heightfield physics shape
				const heightFieldShape = new Ammo.btHeightfieldTerrainShape(
					terrainWidth,
					terrainDepth,
					ammoHeightData,
					heightScale,
					terrainMinHeight,
					terrainMaxHeight,
					upAxis,
					hdt,
					flipQuadEdges
				);

				// Set horizontal scale
				const scaleX = terrainWidthExtents / ( terrainWidth - 1 );
				const scaleZ = terrainDepthExtents / ( terrainDepth - 1 );
				heightFieldShape.setLocalScaling( new Ammo.btVector3( scaleX, 1, scaleZ ) );

				heightFieldShape.setMargin( 0.05 );

				return heightFieldShape;

			}

			function generateObject() {

				const numTypes = 4;
				const objectType = Math.ceil( Math.random() * numTypes );

				let threeObject = null;
				let shape = null;

				const objectSize = 3;
				const margin = 0.05;

				let radius, height;

				switch ( objectType ) {

					case 1:
						// Sphere
						radius = 1 + Math.random() * objectSize;
						threeObject = new THREE.Mesh( new THREE.SphereGeometry( radius, 20, 20 ), createObjectMaterial() );
						shape = new Ammo.btSphereShape( radius );
						shape.setMargin( margin );
						break;
					case 2:
						// Box
						const sx = 1 + Math.random() * objectSize;
						const sy = 1 + Math.random() * objectSize;
						const sz = 1 + Math.random() * objectSize;
						threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), createObjectMaterial() );
						shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
						shape.setMargin( margin );
						break;
					case 3:
						// Cylinder
						radius = 1 + Math.random() * objectSize;
						height = 1 + Math.random() * objectSize;
						threeObject = new THREE.Mesh( new THREE.CylinderGeometry( radius, radius, height, 20, 1 ), createObjectMaterial() );
						shape = new Ammo.btCylinderShape( new Ammo.btVector3( radius, height * 0.5, radius ) );
						shape.setMargin( margin );
						break;
					default:
						// Cone
						radius = 1 + Math.random() * objectSize;
						height = 2 + Math.random() * objectSize;
						threeObject = new THREE.Mesh( new THREE.ConeGeometry( radius, height, 20, 2 ), createObjectMaterial() );
						shape = new Ammo.btConeShape( radius, height );
						break;

				}

				threeObject.position.set( ( Math.random() - 0.5 ) * terrainWidth * 0.6, terrainMaxHeight + objectSize + 2, ( Math.random() - 0.5 ) * terrainDepth * 0.6 );

				const mass = objectSize * 5;
				const localInertia = new Ammo.btVector3( 0, 0, 0 );
				shape.calculateLocalInertia( mass, localInertia );
				const transform = new Ammo.btTransform();
				transform.setIdentity();
				const pos = threeObject.position;
				transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
				const motionState = new Ammo.btDefaultMotionState( transform );
				const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia );
				const body = new Ammo.btRigidBody( rbInfo );

				threeObject.userData.physicsBody = body;

				threeObject.receiveShadow = true;
				threeObject.castShadow = true;

				scene.add( threeObject );
				dynamicObjects.push( threeObject );

				physicsWorld.addRigidBody( body );



			}

			function createObjectMaterial() {

				const c = Math.floor( Math.random() * ( 1 << 24 ) );
				return new THREE.MeshPhongMaterial( { color: c } );

			}

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();

			}

			function render() {

				const deltaTime = clock.getDelta();

				if ( dynamicObjects.length < maxNumObjects && time > timeNextSpawn ) {

					generateObject();
					timeNextSpawn = time + objectTimePeriod;

				}

				updatePhysics( deltaTime );

				renderer.render( scene, camera );

				time += deltaTime;

			}

			function updatePhysics( deltaTime ) {

				physicsWorld.stepSimulation( deltaTime, 10 );

				// Update objects
				for ( let i = 0, il = dynamicObjects.length; i < il; i ++ ) {

					const objThree = dynamicObjects[ i ];
					const objPhys = objThree.userData.physicsBody;
					const ms = objPhys.getMotionState();
					if ( ms ) {

						ms.getWorldTransform( transformAux1 );
						const p = transformAux1.getOrigin();
						const q = transformAux1.getRotation();
						objThree.position.set( p.x(), p.y(), p.z() );
						objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );

					}

				}

			}

}
})